#include "d3d9renderer.h"
#include "renderer.h"

static const DWORD VertexFVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1;
D3D9Renderer* D3D9Renderer::instance = NULL;

typedef HRESULT (__stdcall *EndScene_Type)(IDirect3DDevice9*);
typedef HRESULT (__stdcall *Reset_Type)(IDirect3DDevice9*, D3DPRESENT_PARAMETERS*);

typedef IDirect3D9* (__stdcall *Direct3DCreate9_Type)(UINT);
D3D9Renderer::D3D9Renderer(HINSTANCE hInstance, HMODULE hD3D9, OverlayState const* pState)
  : BaseRenderer(pState)
  , pCurDevice(NULL)
  , pOrigState(NULL)
  , pNewState(NULL)
  , numSourceSizes(8)
{
  instance = this;

  pSourceTextures = new IDirect3DTexture9*[numSourceSizes];
  memset(pSourceTextures, 0, sizeof(IDirect3DTexture9*) * numSourceSizes);

  vertices[0].z = 1.0f; vertices[0].rhw = 1.0f;
  vertices[1].z = 1.0f; vertices[1].rhw = 1.0f;
  vertices[2].z = 1.0f; vertices[2].rhw = 1.0f;
  vertices[3].z = 1.0f; vertices[3].rhw = 1.0f;

  void** pVTable = NULL;

#ifdef _M_IX86

  unsigned char* ptr = (unsigned char*) hD3D9;
  for (int i = 0; i < 0x128000; i++, ptr++)
  {
    if (ptr[0] == 0xC7 && ptr[1] == 0x06 && ptr[6] == 0x89 && ptr[7] == 0x86 && ptr[12] == 0x89 && ptr[13] == 0x86)
    {
      pVTable = *(void***) (ptr + 2);
      break;
    }
  }

#else

  HWND hWnd = CreateDummyWindow(hInstance);
  if (hWnd == NULL) return;

  Direct3DCreate9_Type d3dCreate = (Direct3DCreate9_Type) GetProcAddress(hD3D9, "Direct3DCreate9");
  IDirect3D9* pD3D = (d3dCreate ? d3dCreate(D3D_SDK_VERSION) : NULL);
  if (pD3D == NULL)
  {
    DestroyWindow(hWnd);
    return;
  }

  D3DPRESENT_PARAMETERS pp;
  memset(&pp, 0, sizeof pp);
  pp.Windowed = TRUE;
  pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  pp.BackBufferFormat = D3DFMT_UNKNOWN;
  IDirect3DDevice9 *pDevice;
  if (FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
    D3DCREATE_SOFTWARE_VERTEXPROCESSING, &pp, &pDevice)))
  {
    DestroyWindow(hWnd);
    return;
  }

  pVTable = *(void***) pDevice;

  DestroyWindow(hWnd);
  pDevice->Release();

#endif

  if (pVTable)
  {
    hEndScene.init(pVTable[vtDirect3DDevice9::EndScene], myEndScene);
    hReset.init(pVTable[vtDirect3DDevice9::Reset], myReset);
  }
}

void D3D9Renderer::reset()
{
  BaseRenderer::reset();
  for (int i = 0; i < numSourceSizes; i++)
    COMRELEASE(pSourceTextures[i]);
  COMRELEASE(pOrigState);
  COMRELEASE(pNewState);
}

D3D9Renderer::~D3D9Renderer()
{
  for (int i = 0; i < numSourceSizes; i++)
    COMRELEASE(pSourceTextures[i]);
  COMRELEASE(pOrigState);
  COMRELEASE(pNewState);
  delete[] pSourceTextures;
  instance = NULL;
}

bool D3D9Renderer::updateTexture(BaseTexture* btex, ImageHeader* image)
{
  D3D9Texture* tex = (D3D9Texture*) btex;
  int size = tex->size;
  int sizei = 0;
  if (!tex->pTexture || image->updated)
  {
    size = 1;
    while (size < image->width || size < image->height)
      size <<= 1, sizei++;
    if (tex->pTexture && tex->size != size)
      COMRELEASE(tex->pTexture);
    tex->size = size;
  }
  if (tex->pTexture && !image->updated)
    return true;

  while (sizei >= numSourceSizes)
  {
    IDirect3DTexture9** tmp = new IDirect3DTexture9*[numSourceSizes * 2];
    for (int i = 0; i < numSourceSizes; i++)
      tmp[i] = pSourceTextures[i];
    for (int i = numSourceSizes; i < numSourceSizes * 2; i++)
      tmp[i] = NULL;
    delete[] pSourceTextures;
    pSourceTextures = tmp;
    numSourceSizes *= 2;
  }
  if (pSourceTextures[sizei] == NULL && FAILED(pCurDevice->CreateTexture(size, size, 0, 0,
      D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pSourceTextures[sizei], NULL)))
    return false;
  if (tex->pTexture == NULL && FAILED(pCurDevice->CreateTexture(size, size, 0, 0,
      D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &tex->pTexture, NULL)))
    return false;
  D3DLOCKED_RECT rc;
  if (FAILED(pSourceTextures[sizei]->LockRect(0, &rc, NULL, D3DLOCK_DISCARD)))
    return false;
  unsigned char* dst = (unsigned char*) rc.pBits;
  unsigned long const* src = state->data->bits(image->offset);
  for (unsigned long y = 0; y < image->height; y++)
  {
    memcpy(dst, src, image->width * 4);
    if (size > image->width) memset(dst + image->width * 4, 0, (size - image->width) * 4);
    dst += rc.Pitch;
    src += image->width;
  }
  for (unsigned long y = image->height; y < size; y++)
  {
    memset(dst, 0, size * 4);
    dst += rc.Pitch;
  }
  pSourceTextures[sizei]->UnlockRect(0);

  if (FAILED(tex->pTexture->AddDirtyRect(NULL)) ||
      FAILED(pCurDevice->UpdateTexture(pSourceTextures[sizei], tex->pTexture)))
    return false;
  tex->width = image->width;
  tex->height = image->height;

  image->updated = 0;
  return true;
}
void D3D9Renderer::drawRect(BaseTexture* btex, int left, int top, int right, int bottom, unsigned long color)
{
  D3D9Texture* tex = (D3D9Texture*) btex;
  pCurDevice->SetTexture(0, tex ? tex->pTexture : NULL);
  float x0 = float(left) - 0.5f;
  float y0 = float(top) - 0.5f;
  float x1 = float(right) - 0.5f;
  float y1 = float(bottom) - 0.5f;
  vertices[0].x = x0; vertices[0].y = y0; vertices[0].color = color;
  vertices[1].x = x1; vertices[1].y = y0; vertices[1].color = color;
  vertices[2].x = x1; vertices[2].y = y1; vertices[2].color = color;
  vertices[3].x = x0; vertices[3].y = y1; vertices[3].color = color;
  if (tex)
  {
    float u = float(tex->width) / float(tex->size);
    float v = float(tex->height) / float(tex->size);
    vertices[0].u = 0; vertices[0].v = 0;
    vertices[1].u = u; vertices[1].v = 0;
    vertices[2].u = u; vertices[2].v = v;
    vertices[3].u = 0; vertices[3].v = v;
  }
  pCurDevice->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, vertices, sizeof(Vertex));
}

HRESULT __stdcall D3D9Renderer::myEndScene(IDirect3DDevice9* pDevice)
{
  if (instance == NULL)
    return D3DERR_INVALIDCALL;

  IDirect3DSurface9* pRenderTarget = NULL;
  IDirect3DSurface9* pBackBuffer = NULL;
  if (SUCCEEDED(pDevice->GetRenderTarget(0, &pRenderTarget)) &&
      SUCCEEDED(pDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer)) &&
      pRenderTarget == pBackBuffer)
    instance->render(pDevice);
  COMRELEASE(pRenderTarget);
  COMRELEASE(pBackBuffer);

  instance->hEndScene.unhook();
  HRESULT res = pDevice->EndScene();
  instance->hEndScene.hook();
  return res;
}
HRESULT __stdcall D3D9Renderer::myReset(IDirect3DDevice9* pDevice, D3DPRESENT_PARAMETERS* pParams)
{
  if (instance == NULL)
    return D3DERR_INVALIDCALL;

  if (pDevice == instance->pCurDevice)
    instance->reset();

  instance->hReset.unhook();
  HRESULT res = pDevice->Reset(pParams);
  instance->hReset.hook();
  return res;
}

HWND D3D9Renderer::getDeviceWindow(IDirect3DDevice9* pDevice)
{
  D3DDEVICE_CREATION_PARAMETERS cp;
  if (SUCCEEDED(pDevice->GetCreationParameters(&cp)) && cp.hFocusWindow)
    return cp.hFocusWindow;
  IDirect3DSwapChain9* pSwap;
  if (SUCCEEDED(pDevice->GetSwapChain(0, &pSwap)))
  {
    D3DPRESENT_PARAMETERS pp;
    if (SUCCEEDED(pSwap->GetPresentParameters(&pp)))
      return pp.hDeviceWindow;
  }
  return NULL;
}

void D3D9Renderer::onDestroy(HWND hWnd)
{
  if (pCurDevice && getDeviceWindow(pCurDevice) == hWnd)
    reset();
}

void D3D9Renderer::setViewport(int left, int top, int right, int bottom)
{
  D3DVIEWPORT9 viewport;
  viewport.X = left;
  viewport.Y = top;
  viewport.Width = right - left;
  viewport.Height = bottom - top;
  viewport.MinZ = 0.0f;
  viewport.MaxZ = 2.0f;
  pCurDevice->SetViewport(&viewport);
}

void D3D9Renderer::render(IDirect3DDevice9* pDevice)
{
  SharedData::Lock lock(state->data, 100);
  if (!lock.locked())
    return;
  SharedHeader* hdr = state->data->header();
  if (hdr->hidden)
    return;

  HWND hWnd = getDeviceWindow(pDevice);

  if (pDevice != pCurDevice)
  {
    if (pCurDevice && hdr->hWnd && IsWindow(hdr->hWnd))
    {
      if (hWnd != hdr->hWnd)
      {
        RECT rcOld, rcNew;
        GetClientRect(hdr->hWnd, &rcOld);
        GetClientRect(hWnd, &rcNew);
        if (rcNew.right < rcOld.right || rcNew.bottom < rcOld.bottom)
          return;
      }
    }
    reset();
    pCurDevice = pDevice;
  }

  hdr->hWnd = hWnd;

  if (pOrigState == NULL && !SUCCEEDED(pDevice->CreateStateBlock(D3DSBT_ALL, &pOrigState)))
    return;

  pOrigState->Capture();

  D3DVIEWPORT9 oldViewport;
  IDirect3DBaseTexture9* oldTexture;

  pDevice->GetViewport(&oldViewport);
  pDevice->GetTexture(0, &oldTexture);

  if (pNewState == NULL)
  {
    pDevice->BeginStateBlock();

    pDevice->SetFVF(VertexFVF);
    pDevice->SetVertexShader(NULL);
    pDevice->SetPixelShader(NULL);

    pDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
    pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
    pDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
    pDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
    pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
    pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
    pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    pDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE);
    pDevice->SetRenderState(D3DRS_WRAP0, 0);
    pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
    pDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, 0);
    pDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0x0F);
    pDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

    pDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
    pDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    pDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
    pDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
    pDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_PYRAMIDALQUAD);
    pDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_PYRAMIDALQUAD);
    pDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_NONE);

    pDevice->EndStateBlock(&pNewState);
  }
  pNewState->Apply();

  BaseRenderer::render();

  pDevice->SetViewport(&oldViewport);
  pDevice->SetTexture(0, oldTexture);
  pOrigState->Apply();
}
